package lol.clann.bukkit.pluginboot.model;

import org.bukkit.World;
import org.bukkit.block.Block;

import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;

/**
 * 空间坐标遍历器
 * 递归遍历自身和上下左右前后6个方块
 * 通过Predicate<Block>来控制边界条件
 *
 * @author pyz
 * @date 2018/8/18 下午5:40
 */
public class SpaceWalker {
    /**
     * 有效位置
     */
    private Set<ImmutableLocation> walked = new HashSet<>();
    /**
     * 无效位置
     */
    private Set<ImmutableLocation> invalid = new HashSet<>();
    private World world;
    private int x;
    private int y;
    private int z;
    /**
     * 返回true表示有效方块
     */
    private Predicate<Block> test;

    private SpaceWalker(World world, int x, int y, int z, Predicate<Block> test) {
        this.world = world;
        this.x = x;
        this.y = y;
        this.z = z;
        this.test = test;
    }

    public static List<Block> walk(World world, int x, int y, int z, Predicate<Block> test) {
        return new SpaceWalker(world, x, y, z, test)
                .walk();
    }

    private List<Block> walk() {
        walk(x, y, z);
        return walked.stream()
                .map(ImmutableLocation::getBlock)
                .collect(Collectors.toList());
    }

    private void walk(int x, int y, int z) {
        ImmutableLocation loc = new ImmutableLocation(x, y, z);
        if (walked.contains(loc)) {
            return;
        }
        if (invalid.contains(loc)) {
            return;
        }
        Block block = world.getBlockAt(x, y, z);
        loc.setBlock(block);
        if (!test.test(block)) {
            invalid.add(loc);
            return;
        }
        walked.add(loc);
        walk(x + 1, y, z);
        walk(x - 1, y, z);
        walk(x, y + 1, z);
        walk(x, y - 1, z);
        walk(x, y, z + 1);
        walk(x, y, z - 1);
        return;
    }

    private static class ImmutableLocation {
        private final int x, y, z;
        private Block block;

        public ImmutableLocation(int x, int y, int z) {
            this.x = x;
            this.y = y;
            this.z = z;
        }

        public Block getBlock() {
            return block;
        }

        public void setBlock(Block block) {
            this.block = block;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            ImmutableLocation that = (ImmutableLocation) o;
            return x == that.x &&
                    y == that.y &&
                    z == that.z;
        }

        @Override
        public int hashCode() {

            return Objects.hash(x, y, z);
        }
    }
}
